package com.autumn.platform.web.jsonbody;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.domain.Page;

import com.autumn.platform.core.Utils;
import com.autumn.platform.core.spring.SpringHolder;
import com.autumn.platform.web.annotation.JsonBody;
import com.autumn.platform.web.annotation.JsonClass;
import com.autumn.platform.web.annotation.JsonField;
import com.autumn.platform.web.jsonbody.serial.config.IJsonSerialConfig;
import com.autumn.platform.web.jsonbody.serial.config.JsonSerialConfigBuilder;
import com.autumn.platform.web.jsonbody.serial.converter.IJsonConverter;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;

public class JsonBodySupport {

    /**
     * 当前线程的JsonBody注解
     */
    private static final ThreadLocal<JsonBody> jsonBody = new ThreadLocal<JsonBody>();

    private static final ThreadLocal<TypeDescriptor> typeDescriptor = new ThreadLocal<TypeDescriptor>();

    public static void setJsonBody(Method method) {
        JsonBody jsonBody = method.getAnnotation(JsonBody.class);
        if (useSpringJackson(method, jsonBody)) {
            JsonBodySupport.jsonBody.remove();
        } else {
            JsonBodySupport.jsonBody.set(jsonBody);
        }
    }

    public static boolean hasJsonBody() {
        return jsonBody.get() != null;
    }

    public static void setTypeDescriptor(TypeDescriptor typeDescriptor) {
        JsonBodySupport.typeDescriptor.set(typeDescriptor);
    }

    public static void writeWithJsonBody(OutputStream out, Object target) {
        try {
            JsonBody jsonBody = getJsonBody();
            if (null == jsonBody) {
                JsonUtils.serialize(out, target);
            } else {
                TypeDescriptor type = getTypeDescriptor();
                String[] fields = jsonBody.fields();
                JsonField[] jsonFields = jsonBody.jsonFields();
                JsonClass[] jsonClasses = jsonBody.classes();
                boolean hasJsonClasses = null != jsonClasses && jsonClasses.length > 0;
                boolean hasJsonFields = null != fields && fields.length > 0 || null != jsonFields && jsonFields.length > 0;
                if (null == target) {
                    JsonUtils.serialize(out, target);
                } else if (!hasJsonClasses && !hasJsonFields) {
                    JsonUtils.serialize(out, target);
                } else {
                    int clsNum = 0;
                    JsonSerialConfigBuilder builder = JsonSerialConfigBuilder.start();
                    if (hasJsonFields) {
                        clsNum++;
                        Class<?> serialType = null;
                        if (type.isCollection()) {
                            serialType = type.getElementTypeDescriptor().getType();
                        } else if (type.isMap()) {
                            serialType = type.getMapValueTypeDescriptor().getType();
                        } else if (Page.class.isAssignableFrom(type.getType())) {
                            serialType = type.getResolvableType().resolveGeneric(0);
                        } else {
                            serialType = type.getType();
                        }
                        setJsonFields(serialType, fields, jsonFields, builder);
                    }

                    if (hasJsonClasses) {
                        for (JsonClass jsonClass : jsonClasses) {
                            if (clsNum > 0) {
                                builder = builder.newInstance();
                            }
                            clsNum++;
                            setJsonFields(jsonClass.cls(), jsonClass.fields(), jsonClass.jsonFields(), builder);
                        }
                    }

                    IJsonSerialConfig[] config = builder.builds();
                    JsonUtils.serialize(out, target, config);
                }
            }
        } catch (JsonGenerationException e) {
            e.printStackTrace();
        } catch (JsonMappingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            remove();
        }
    }

    public static void remove() {
        removeJsonBody();
        removeTypeDescriptor();
    }

    private static JsonBody getJsonBody() {
        return jsonBody.get();
    }

    private static void removeJsonBody() {
        jsonBody.remove();
    }

    private static TypeDescriptor getTypeDescriptor() {
        return typeDescriptor.get();
    }

    private static void removeTypeDescriptor() {
        typeDescriptor.remove();
    }

    /**
     * 是否使用Spring的Jackson封装
     * 
     * @param method
     * @param jsonBody
     * @return
     */
    private static boolean useSpringJackson(Method method, JsonBody jsonBody) {
        if (null == jsonBody) {
            return true;
        }

        String value = jsonBody.value();
        if (String.class.isAssignableFrom(method.getReturnType()) && !Utils.isBlank(value)) {
            return false;
        }

        String[] fields = jsonBody.fields();
        if (fields != null && fields.length >= 1) {
            return false;
        }

        JsonField[] jsonFields = jsonBody.jsonFields();
        if (jsonFields != null && jsonFields.length >= 1) {
            return false;
        }

        JsonClass[] classes = jsonBody.classes();
        if (classes != null && classes.length >= 1) {
            return false;
        }
        return true;
    }

    private static void setJsonFields(Class<?> serialType, String[] fields, JsonField[] jsonFields, JsonSerialConfigBuilder builder) {
        builder.serialType(serialType);
        Set<String> fieldSet = new HashSet<String>();
        if (null != jsonFields && jsonFields.length > 0) {
            for (JsonField field : jsonFields) {
                resolverJsonField(fieldSet, field, builder);
            }
        }
        if (null != fields && fields.length > 0) {
            for (String field : fields) {
                resolverShortField(fieldSet, field, builder);
            }
        }
    }

    private static void resolverShortField(Set<String> fieldSet, String field, JsonSerialConfigBuilder builder) {
        String[] cfgs = field.split("\\s*#\\s*");
        String name = cfgs[0];
        if (fieldSet.contains(name)) {// 已经有配置，忽略不计
            return;
        }
        if (cfgs.length == 1) {
            builder.addIncludeProperty(name);
        } else {
            boolean exclude = false;
            boolean alias = false;
            String cfg1 = cfgs[1].toLowerCase();
            if ("false".equals(cfg1) || "0".equals(cfg1)) {
                builder.addExcludeProperty(name);
                exclude = true;
            } else if ("true".equals(cfg1) || "1".equals(cfg1)) {
                builder.addIncludeProperty(name);
            } else {
                alias = true;
                builder.addAlias(name, cfgs[1]);
            }

            if (cfgs.length >= 3 && !exclude) {
                String convertBean = cfgs[2];
                if (!Utils.isBlank(convertBean)) {
                    if (!alias) {// 如果有转换器，并且没有别名，就使用原名称作为别名，从而阻止开启肯定筛选模式
                        builder.addAlias(name, name);
                    }
                    builder.addConverter(name, SpringHolder.getApplicationContext().getBean(convertBean, IJsonConverter.class));
                }
            }
        }
    }

    private static void resolverJsonField(Set<String> fieldSet, JsonField field, JsonSerialConfigBuilder builder) {
        String name = field.value();
        if (fieldSet.contains(name)) {// 已经有配置，忽略不计
            return;
        }
        if (field.ignore()) {
            builder.addExcludeProperty(name);
        } else {
            boolean hasConvert = false;
            String convertBean = field.convertBeanName();
            if (!Utils.isBlank(convertBean)) {
                hasConvert = true;
                builder.addConverter(name, SpringHolder.getApplicationContext().getBean(convertBean, IJsonConverter.class));
            }

            String alias = field.alias();
            if (!Utils.isBlank(alias)) {
                builder.addAlias(name, alias);
            } else if (hasConvert) {// 如果有转换器，这里使用别名，从而阻止开启肯定筛选模式
                builder.addAlias(name, name);
            } else {
                builder.addIncludeProperty(name);
            }
        }
    }
}
